package fr.inserm.umr915.vcfserver.j2ee;

import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;


import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.XMLConstants;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;

import com.sleepycat.bind.tuple.StringBinding;
import com.sleepycat.bind.tuple.TupleBinding;
import com.sleepycat.je.Cursor;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.LockMode;
import com.sleepycat.je.OperationStatus;

import fr.inserm.umr915.vcfserver.bio.Segment;
import fr.inserm.umr915.vcfserver.bio.berkeley.DatabaseId;
import fr.inserm.umr915.vcfserver.bio.berkeley.DatabaseManager;
import fr.inserm.umr915.vcfserver.bio.berkeley.Storage;
import fr.inserm.umr915.vcfserver.bio.segparser.SegmentParser;
import fr.inserm.umr915.vcfserver.lang.LoopControl;
import fr.inserm.umr915.vcfserver.util.ColumnSerializer;
import fr.inserm.umr915.vcfserver.xml.XsdSerializer;

public  class GenericDBServlet  extends HttpServlet
	{
	private static final long serialVersionUID = 1L;
	
	
	@Override
	protected final void doGet(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		doPost(req, resp);
		}
	
	@Override
	@SuppressWarnings({ "rawtypes" })
	protected void doPost(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException
		{
		Path path=new Path(req.getPathInfo());
		WebApplicationContext factory = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext());
		Storage storage=(Storage)this.getServletContext().getAttribute("berkeley.db.environment");
		if(storage==null) throw new ServletException("Cannot find storage ");
		
		String suffix="";
		String managerName=path.get(0);
		int dot=managerName.indexOf('.');
		if(dot!=-1)
			{
			suffix=managerName.substring(dot+1).toLowerCase();
			managerName=managerName.substring(0,dot);
			}
		
		DatabaseManager<?> manager=storage.getNameToMap().get(path.get(0));
		if(manager==null)
			{
			req.setAttribute("managers", storage.getIdToMap().values());
			req.getRequestDispatcher("/WEB-INF/jsp/pages/lookup.jsp").forward(req,resp);
			return;
			}
		if(suffix.equals("xsd") && manager.getXsdSerializer()!=null)
			{
			printXsd(req,resp,manager.getXsdSerializer());
			return;
			}
		
		
		String names[]=req.getParameterValues("name");
		if(names!=null)
			{
			Set<String> namesUpper =new HashSet<String>();
			for(String s:names) namesUpper.add(s.toUpperCase());
			namesUpper.remove("");
			findByName(req, resp, factory,storage,manager, namesUpper);
			}
		else if(req.getParameter("segments")!=null)
			{
			Set<Segment> segments=new TreeSet<Segment>(SegmentParser.parseMany(req.getParameter("segments")));
			inRangeList(req,resp,factory,storage,manager,segments);
			}
		else
			{
			String pos=req.getParameter("pos");
			if(pos==null) pos=req.getParameter("segment");
			if(pos==null) pos=req.getParameter("q");
			if(pos==null) pos=req.getParameter("position");
			if(pos==null) pos=req.getParameter("loc");
			if(pos==null)
				{
				List<DatabaseManager> L=new ArrayList<DatabaseManager>();
				L.add(manager);
				req.setAttribute("managers", L);
				req.getRequestDispatcher("/WEB-INF/jsp/pages/lookup.jsp").forward(req,resp);
				return;
				}
			Segment segment=SegmentParser.parseOne(pos);
			
			String s2= req.getParameter("exists");
			if(s2!=null && !s2.equals("false"))
				{
				inRangeExists(req,resp,factory,storage,manager,segment);
				return;
				}
			s2= req.getParameter("count");
			if(s2!=null && !s2.equals("false"))
				{
				inRangeCount(req,resp,factory,storage,manager,segment);
				return;
				}
			inRange(req,resp,factory,storage,manager,segment);
			}
		
		}

	/** print xsd schema, if any */
	protected void printXsd(
			HttpServletRequest req,
			HttpServletResponse resp,
			XsdSerializer xsd
			) throws IOException,ServletException
		{
		String encoding=resp.getCharacterEncoding();
		if(encoding==null) encoding="UTF-8";
		resp.setContentType("text/xml");
		OutputStream out=resp.getOutputStream();
		
		try {
			Transformer tr=TransformerFactory.newInstance().newTransformer();
			tr.setOutputProperty(OutputKeys.ENCODING, encoding);
			tr.transform(new DOMSource(xsd.create()), new StreamResult(out));
			}
		catch (Exception e)
			{
			throw new ServletException(e);
			}
		finally
			{
			out.flush();
			out.close();
			}
		
		}
	
	@SuppressWarnings({ "rawtypes", "unchecked" })
	protected void inRange(HttpServletRequest req, HttpServletResponse resp,
			BeanFactory factory,
			Storage storage,
			DatabaseManager manager,
			Segment segment)
			throws ServletException, IOException
		{
		String build= (String)factory.getBean("genome.build");
		
		Integer longestLength= storage.getLongest(manager.getId());
		if(longestLength==null) longestLength=1000;
		Segment seg2 =new Segment(segment.getChromosome(),
				Math.max(segment.getChromStart()-longestLength,0),
				segment.getChromEnd());
		Cursor cursor=null;
		Segment.Binding segmentBinding=new Segment.Binding(storage.getChromosomes());
		TupleBinding dataBinding=manager.getDataBinding();
		DatabaseId.Binding  dbidBinding= new DatabaseId.Binding();
		try
			{
			boolean first=true;
			DatabaseEntry key=new DatabaseEntry();
			DatabaseEntry data=new DatabaseEntry();
			DatabaseEntry entry3=new DatabaseEntry();
			
			segmentBinding.objectToEntry(seg2,key);
			String encoding=resp.getCharacterEncoding();
			if(encoding==null) encoding="UTF-8";
			resp.setContentType("text/xml");
			OutputStream out=resp.getOutputStream();
			XMLOutputFactory xmlfactory= XMLOutputFactory.newInstance();
			XMLStreamWriter w= xmlfactory.createXMLStreamWriter(out,encoding);
			w.writeStartDocument(encoding,"1.0");
			w.writeStartElement(manager.getListName());
			w.writeAttribute("build", build);
			w.writeAttribute("query", segment.toString());
			hasXsdDecl(w,req,manager);
			
			//w.writeComment("long is "+longestLength);
			//w.writeComment("starting with "+seg2.toString());
			cursor= storage.loc2id.openCursor(null,null);
			for(;;)
				{
				OperationStatus status;
				if(first)
					{
					status=cursor.getSearchKeyRange(key, data, LockMode.DEFAULT);
					}
				else
					{
					status=cursor.getNext(key, data, LockMode.DEFAULT);
					}
				first=false;
				
				if(status!=OperationStatus.SUCCESS) break;
				
				
				seg2= segmentBinding.entryToObject(key);
				
				LoopControl ctrl= Segment.scanner(segment, seg2);
				if(ctrl==LoopControl.CONTINUE)
					{
					//w.writeComment(seg2.toString());
					continue;
					}
				if(ctrl==LoopControl.BREAK)
					{
					//w.writeComment(seg2.toString());
					break;
					}
				
				DatabaseId dbId=dbidBinding.entryToObject(data);
				if(dbId.getDatabase()!=manager.getId())
					{
					continue;
					}
				
				status=storage.id2object.get(null, data, entry3, LockMode.DEFAULT);
				if(status!=OperationStatus.SUCCESS)
					{
					//UHHHH????
					//w.writeComment("???");
					continue;
					}
				
				
				Object o=dataBinding.entryToObject(entry3);
				manager.getXmlStreamSerializer().write(o, w);
				}
			w.writeEndElement();
			w.writeEndDocument();
			w.flush();
			w.close();
			out.flush();
			out.close();
			}
		catch (Exception e)
			{
			throw new ServletException(e);
			}
		finally
			{
			if(cursor!=null) cursor.close();
			}
		}
	
	
	
	@SuppressWarnings({ "rawtypes", "unchecked" })
	protected void findByName(HttpServletRequest req, HttpServletResponse resp,
				BeanFactory factory,
				Storage storage,
				DatabaseManager manager,
				Set<String> names)
	throws ServletException, IOException
		{
		Cursor cursor=null;
		String build= (String)factory.getBean("genome.build");
		
		
		TupleBinding<Object> dataBinding=manager.getDataBinding();
		DatabaseId.Binding  dbidBinding= new DatabaseId.Binding();
		try
			{
			
			
			String encoding=resp.getCharacterEncoding();
			if(encoding==null) encoding="UTF-8";
			OutputStream out=resp.getOutputStream();
			XMLOutputFactory xmlfactory= XMLOutputFactory.newInstance();
			XMLStreamWriter w= xmlfactory.createXMLStreamWriter(out,encoding);
			w.writeStartDocument(encoding,"1.0");
			w.writeStartElement(manager.getListName());
			w.writeAttribute("build", build);
			hasXsdDecl(w,req,manager);
			
			
			
			for(String name:names)
				{
				boolean first=true;
				DatabaseEntry key=new DatabaseEntry();
				DatabaseEntry data=new DatabaseEntry();
				DatabaseEntry entry3=new DatabaseEntry();
				StringBinding.stringToEntry(name.toUpperCase(), key);
				cursor= storage.name2id.openCursor(null,null);
				for(;;)
					{
					OperationStatus status;
					if(first)
						{
						status=cursor.getSearchKey(key, data, LockMode.DEFAULT);
						}
					else
						{
						status=cursor.getNextDup(key, data, LockMode.DEFAULT);
						}
					first=false;
					if(status!=OperationStatus.SUCCESS) break;
					
					DatabaseId dbId=dbidBinding.entryToObject(data);
					if(dbId.getDatabase()!=manager.getId()) continue;
					
					status=storage.id2object.get(null, data, entry3, LockMode.DEFAULT);
					if(status!=OperationStatus.SUCCESS)
						{
						//UHHHH????
						continue;
						}
					
					
					Object o=dataBinding.entryToObject(entry3);
					manager.getXmlStreamSerializer().write(o, w);
					}
				cursor.close();
				cursor=null;
				}
			w.writeEndElement();
			w.writeEndDocument();
			w.flush();
			w.close();
			out.flush();
			out.close();
			}
		catch (Exception e)
			{
			throw new ServletException(e);
			}
		finally
			{
			if(cursor!=null) cursor.close();
			}
		}
	
	@SuppressWarnings("rawtypes")
	private void hasXsdDecl(XMLStreamWriter w,HttpServletRequest req,DatabaseManager manager)
		throws XMLStreamException,IOException
		{
		
		if(manager.getXsdSerializer()==null) return;
		w.writeAttribute("xmlns:xsi", XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI);
		w.writeAttribute("xsi:noNamespaceSchemaLocation",
				new URL(new URL(req.getRequestURL().toString()),manager.getName()+".xsd").toString()
				);
		}
	
	
	@SuppressWarnings({ "rawtypes" })
	protected void inRangeExists(HttpServletRequest req, HttpServletResponse resp,
			BeanFactory factory,
			Storage storage,
			DatabaseManager manager,
			Segment segment)
			throws ServletException, IOException
		{
		String build= (String)factory.getBean("genome.build");
		
		Integer longestLength= storage.getLongest(manager.getId());
		if(longestLength==null) longestLength=0;
		Segment seg2 =new Segment(segment.getChromosome(),
				Math.max(segment.getChromStart()-longestLength,0),
				segment.getChromEnd());
		Cursor cursor=null;
		Segment.Binding segmentBinding=new Segment.Binding(storage.getChromosomes());
		
		DatabaseId.Binding  dbidBinding= new DatabaseId.Binding();
		try
			{
			boolean first=true;
			DatabaseEntry key=new DatabaseEntry();
			DatabaseEntry data=new DatabaseEntry();
			boolean exists=false;
			
			segmentBinding.objectToEntry(seg2,key);
			
			
			
			cursor= storage.loc2id.openCursor(null,null);
			for(;;)
				{
				OperationStatus status;
				if(first)
					{
					status=cursor.getSearchKeyRange(key, data, LockMode.DEFAULT);
					}
				else
					{
					status=cursor.getNext(key, data, LockMode.DEFAULT);
					}
				first=false;
				
				if(status!=OperationStatus.SUCCESS) break;
				
				
				seg2= segmentBinding.entryToObject(key);
				
				LoopControl ctrl= Segment.scanner(segment, seg2);
				if(ctrl==LoopControl.CONTINUE) continue;
				if(ctrl==LoopControl.BREAK) break;
				
				DatabaseId dbId=dbidBinding.entryToObject(data);
				if(dbId.getDatabase()!=manager.getId()) continue;
				exists=true;
				break;
				}
			String encoding=resp.getCharacterEncoding();
			if(encoding==null) encoding="UTF-8";
			resp.setContentType("text/xml");
			OutputStream out=resp.getOutputStream();
			XMLOutputFactory xmlfactory= XMLOutputFactory.newInstance();
			XMLStreamWriter w= xmlfactory.createXMLStreamWriter(out,encoding);
			w.writeStartDocument(encoding,"1.0");
			w.writeStartElement("Exists");
			w.writeAttribute("build", build);
			w.writeAttribute("segment", segment.toString());
			w.writeAttribute("database", manager.getName());
			w.writeCharacters(String.valueOf(exists));
			w.writeEndElement();
			w.writeEndDocument();
			w.flush();
			w.close();
			out.flush();
			out.close();
			}
		catch (Exception e)
			{
			throw new ServletException(e);
			}
		finally
			{
			if(cursor!=null) cursor.close();
			}
		}
	
	
	@SuppressWarnings({ "rawtypes" })
	protected void inRangeCount(HttpServletRequest req, HttpServletResponse resp,
			BeanFactory factory,
			Storage storage,
			DatabaseManager manager,
			Segment segment)
			throws ServletException, IOException
		{
		String build= (String)factory.getBean("genome.build");
		
		Integer longestLength= storage.getLongest(manager.getId());
		if(longestLength==null) longestLength=0;
		Segment seg2 =new Segment(segment.getChromosome(),
				Math.max(segment.getChromStart()-longestLength,0),
				segment.getChromEnd());
		Cursor cursor=null;
		Segment.Binding segmentBinding=new Segment.Binding(storage.getChromosomes());
		
		DatabaseId.Binding  dbidBinding= new DatabaseId.Binding();
		try
			{
			boolean first=true;
			DatabaseEntry key=new DatabaseEntry();
			DatabaseEntry data=new DatabaseEntry();
			long count=0L;
			
			segmentBinding.objectToEntry(seg2,key);
			
			
			
			cursor= storage.loc2id.openCursor(null,null);
			for(;;)
				{
				OperationStatus status;
				if(first)
					{
					status=cursor.getSearchKeyRange(key, data, LockMode.DEFAULT);
					}
				else
					{
					status=cursor.getNext(key, data, LockMode.DEFAULT);
					}
				first=false;
				
				if(status!=OperationStatus.SUCCESS) break;
				
				
				seg2= segmentBinding.entryToObject(key);
				LoopControl ctrl= Segment.scanner(segment, seg2);
				if(ctrl==LoopControl.CONTINUE) continue;
				if(ctrl==LoopControl.BREAK) break;
				
				DatabaseId dbId=dbidBinding.entryToObject(data);
				if(dbId.getDatabase()!=manager.getId()) continue;
				count++;
				}
			String encoding=resp.getCharacterEncoding();
			if(encoding==null) encoding="UTF-8";
			resp.setContentType("text/xml");
			OutputStream out=resp.getOutputStream();
			XMLOutputFactory xmlfactory= XMLOutputFactory.newInstance();
			XMLStreamWriter w= xmlfactory.createXMLStreamWriter(out,encoding);
			w.writeStartDocument(encoding,"1.0");
			w.writeStartElement("Count");
			w.writeAttribute("build", build);
			w.writeAttribute("segment", segment.toString());
			w.writeAttribute("database", manager.getName());
			w.writeCharacters(String.valueOf(count));
			w.writeEndElement();
			w.writeEndDocument();
			w.flush();
			w.close();
			out.flush();
			out.close();
			}
		catch (Exception e)
			{
			throw new ServletException(e);
			}
		finally
			{
			if(cursor!=null) cursor.close();
			}
		}
	
	@SuppressWarnings({ "rawtypes", "unchecked" })
	protected void inRangeList(HttpServletRequest req, HttpServletResponse resp,
			BeanFactory factory,
			Storage storage,
			DatabaseManager manager,
			Set<Segment> segments)
			throws ServletException, IOException
		{
		boolean onlyOne=false;
		if("true".equals(req.getParameter("onlyone")))
			{
			onlyOne=true;
			}
		Segment.Binding segmentBinding=new Segment.Binding(storage.getChromosomes());
		TupleBinding dataBinding=manager.getDataBinding();
		Cursor cursor=null;
		Integer longestLength= storage.getLongest(manager.getId());
		if(longestLength==null)
			{
			longestLength=1000;
			if(req.getParameter("expect.length")!=null)
				{
				longestLength=Integer.parseInt(req.getParameter("expect.length"));
				}
			}
		DatabaseId.Binding  dbidBinding= new DatabaseId.Binding();
		
		DatabaseEntry key=new DatabaseEntry();
		DatabaseEntry data=new DatabaseEntry();
		DatabaseEntry entry3=new DatabaseEntry();
		
		
		resp.setContentType("text/plain");
		PrintWriter out=resp.getWriter();
		out.print("#query\ttarget");
		ColumnSerializer delimited= manager.getColumnSerializer();
		if(delimited!=null)
			{
			String cols[]=delimited.getHeaders();
			for(String col:cols) out.print("\t"+col);
			}
		out.println();
		try
			{
			cursor= storage.loc2id.openCursor(null,null);
			for(Segment segment:segments)
				{
				boolean found=false;
				Segment seg2 =new Segment(segment.getChromosome(),
						Math.max(segment.getChromStart()-longestLength,0),
						segment.getChromEnd());
				boolean first=true;
				
				segmentBinding.objectToEntry(seg2,key);
				
				for(;;)
					{
					OperationStatus status;
					if(first)
						{
						status=cursor.getSearchKeyRange(key, data, LockMode.DEFAULT);
						}
					else
						{
						status=cursor.getNext(key, data, LockMode.DEFAULT);
						}
					first=false;
					
					if(status!=OperationStatus.SUCCESS) break;
					
					seg2= segmentBinding.entryToObject(key);
					
					LoopControl ctrl= Segment.scanner(segment, seg2);
					if(ctrl==LoopControl.CONTINUE)
						{
						continue;
						}
					if(ctrl==LoopControl.BREAK)
						{
						break;
						}
					
					DatabaseId dbId=dbidBinding.entryToObject(data);
					if(dbId.getDatabase()!=manager.getId())
						{
						continue;
						}
					
					found=true;
					out.print(segment.toString());
					out.print('\t');
					out.print(seg2.toString());
					if(delimited!=null)
						{
						status=storage.id2object.get(null, data, entry3, LockMode.DEFAULT);
						if(status==OperationStatus.SUCCESS)
							{
							Object o=dataBinding.entryToObject(entry3);
							Object[] cols=delimited.getColumns(o);
							for(Object o2:cols)
								{
								out.print('\t');
								out.print(String.valueOf(o2));
								}
							}
						
						}
					out.println();
					if(onlyOne) break;
					}
				if(!found)
					{
					out.println(segment.toString());
					}
				}
			out.flush();
			out.close();
			}
		catch (Exception e)
			{
			throw new ServletException(e);
			}
		finally
			{
			if(cursor!=null) cursor.close();
			}
		}
	
	
	}
